#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/11/20 8:36
# @Author  : huidong.bai
# @File    : test4.py
# @Software: PyCharm

import ctypes
import logging
from ctypes import wintypes
from PyQt5 import QtCore, QtWidgets

RegisterDeviceNotification = ctypes.windll.user32.RegisterDeviceNotificationW
UnregisterDeviceNotification = ctypes.windll.user32.UnregisterDeviceNotification


class GUID(ctypes.Structure):
    _fields_ = [
        ('Data1', wintypes.DWORD),
        ('Data2', wintypes.WORD),
        ('Data3', wintypes.WORD),
        ('Data4', wintypes.BYTE * 8),
    ]

    def __str__(self):
        return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format(
            self.Data1,
            self.Data2,
            self.Data3,
            ''.join(["{:02x}".format(d) for d in self.Data4[:2]]),
            ''.join(["{:02x}".format(d) for d in self.Data4[2:]]),
        )


class DEV_BROADCAST_PORT(ctypes.Structure):
    _pack_ = 1
    _fields_ = [("dbcp_size", wintypes.DWORD),
                ("dbcp_devicetype", wintypes.DWORD),
                ("dbcp_reserved", wintypes.DWORD),
                ("dbcp_name", wintypes.WCHAR * 256)]


class DEV_BROADCAST_HDR(ctypes.Structure):
    _fields_ = [("dbch_size", wintypes.DWORD),
                ("dbch_devicetype", wintypes.DWORD),
                ("dbch_reserved", wintypes.DWORD)]


DBT_DEVTYP_PORT = 0x00000003
DBT_DEVICEREMOVECOMPLETE = 0x8004
DBT_DEVICEARRIVAL = 0x8000
WM_DEVICECHANGE = 0x0219
DBT_DEVTYP_DEVICEINTERFACE = 5
DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 0x00000004

logger = logging.getLogger("SerialEnumerator")


class SerialEnumerator(QtWidgets.QWidget):
    device_discovered = QtCore.pyqtSignal(str)
    device_removed = QtCore.pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.setupNotification()

    def setupNotification(self):
        dbp = DEV_BROADCAST_PORT()
        dbp.dbcp_size = ctypes.sizeof(DEV_BROADCAST_PORT)
        dbp.dbcp_devicetype = DBT_DEVTYP_DEVICEINTERFACE
        self.hNofity = RegisterDeviceNotification(int(self.winId()),
                                                  ctypes.byref(dbp),
                                                  DEVICE_NOTIFY_WINDOW_HANDLE)
        if self.hNofity == 0:
            logger.debug(ctypes.FormatError(), int(self.winId()))
            logger.debug("RegisterDeviceNotification failed")

    def closeEvent(self, event):
        super().closeEvent(event)
        if self.hNofity:
            UnregisterDeviceNotification(self.hNofity)

    def nativeEvent(self, eventType, message):
        retval, result = super().nativeEvent(eventType, message)
        if eventType == "windows_generic_MSG":
            msg = wintypes.MSG.from_address(message.__int__())
            if msg.message == WM_DEVICECHANGE:
                if DBT_DEVICEARRIVAL == msg.wParam or DBT_DEVICEREMOVECOMPLETE == msg.wParam:
                    dbh = DEV_BROADCAST_HDR.from_address(msg.lParam)
                    if dbh.dbch_devicetype == DBT_DEVTYP_PORT:
                        dbd = DEV_BROADCAST_PORT.from_address(msg.lParam)
                        if DBT_DEVICEARRIVAL == msg.wParam:
                            self.device_discovered.emit(dbd.dbcp_name)
                        elif DBT_DEVICEREMOVECOMPLETE == msg.wParam:
                            self.device_removed.emit(dbd.dbcp_name)
        return retval, result
